/**********************************************************************
*  Copyright (c) 2007 - Victor Jacobs - victor.jacobs@gmail.com
*
*  Permission is hereby granted, free of charge, to any person
*  obtaining a copy of this software and associated documentation
*  files (the "Software"), to deal in the Software without
*  restriction, including without limitation the rights to use,
*  copy, modify, merge, publish, distribute, sublicense, and/or sell
*  copies of the Software, and to permit persons to whom the
*  Software is furnished to do so, subject to the following
*  conditions:
*
*  The above copyright notice and this permission notice shall be
*  included in all copies or substantial portions of the Software.
*
*  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
*  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
*  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
*  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
*  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
*  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
*  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
*  OTHER DEALINGS IN THE SOFTWARE.
**********************************************************************/

using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using Vj.MathLibrary;
using Vj.MathLibrary.Binary;

namespace Vj.GA
{
    public class Allele
    {
        private int[] myBitList;
        private int myLength;
        private double myValue;

        public Allele(int size)
        {
            myLength = size;
            myBitList = new int[size];
            Randomize();
            CalculateValue(true);
        }

        public Allele(int[] bList)
        {
            myBitList = bList;
            myLength = bList.Length;
            CalculateValue(true);
        }

        public int this[int index]
        {
            get { return myBitList[index]; }
            set { myBitList[index] = value; }
        }

        public int Length
        {
            get { return myLength; }
            set { myLength = value; }
        }

        public double Value
        {
            get 
            {
                CalculateValue(true);
                return myValue; 
            }
            //set { myValue = value; }
        }

        public void Clear()
        {
            myBitList = new int[myLength];
        }

        public void Randomize()
        {
            for (int i = 0; i < Length; i++)
                myBitList[i] = RandomGrabber.Next(2);
        }

        public int[] GetArray()
        {
            return myBitList;
        }

        private void CalculateValue(bool shift)
        {
            double val = (double)BitList. Binary.BinToInt(myBitList);
            
            val = 250.0 * ((double)val / Math.Pow(2, Length));

            if(shift)
                val -= 250.0/2.0;

            myValue = val;
        }

    }
    // genome class
    public class Genome
    {
        static private int currentID = 0;

        private double myFitness;
        private double scaledFitness;
        private double accumulatedFitness;
        private int myID;
               
        private int numOfAlleles;
        private int myLength;

        private int[] myBitSequence;
        private Allele[] alleleList;
        private int[] alleleValues;

        /// <summary>
        /// genome constructor
        /// </summary>
        /// <param name="aList"></param>
        public Genome(Allele[] aList)
        {
            if (aList == null)
                throw new Exception("Empty Allele List");

            myID = currentID++;
            alleleList = aList;
            
            int alleleLength = aList[0].Length;
            numOfAlleles = aList.Length;
            myLength = alleleLength * numOfAlleles;
            myBitSequence = new int[myLength];
            alleleValues = new int[numOfAlleles];
            AssembleBitSeq();
        }

        public Genome(int numOfAl, int alLength)
        {
            if (numOfAl <= 0 || alLength <= 0)
                throw new Exception("Invalid Allele Parameter");
            
            myID = currentID++;
            
            numOfAlleles = numOfAl;

            alleleList = new Allele[numOfAl];

            for (int i = 0; i < numOfAlleles; i++)
            {
                alleleList[i] = new Allele(alLength);
            }
                      
            myLength = alLength * numOfAlleles;
            myBitSequence = new int[myLength];
            alleleValues = new int[numOfAlleles];

            AssembleBitSeq(); 
        }

        static public void ResetID()
        {
            currentID = 0;
        }

        public int Length
        {
            //set { myLength = value; }
            get { return myLength; }
        }

        public int NumOfAlleles
        {
            //set { numOfAlleles = value; }
            get { return numOfAlleles; }
        }

        public int MyID
        {
            get { return myID; }
            set { myID = value; }
        }

        public double Fitness
        {
            get { return myFitness; }
            set { myFitness = value; }
        }

        public double ScaledFitness
        {
            get { return scaledFitness; }
            set { scaledFitness = value; }
        }

        public double AccumulatedFitness
        {
            get { return accumulatedFitness; }
            set { accumulatedFitness = value; }
        }


        //public int[] AlleleValues
        //{
        //    get 
        //    {
        //        for (int i = 0; i < alleleList.Length; i++)
        //            alleleValues[i] = alleleList[i].Value;
        //        return alleleValues; 
        //    }
        //}

        public double[] AlleleValuesD
        {
            get
            {
                double []aVals = new double[alleleList.Length];
                for (int i = 0; i < alleleList.Length; i++)
                    aVals[i] = (double)alleleList[i].Value;
                return aVals;
            }
        }

        public int this[int index]
        {
            get {
                return myBitSequence[index];
             }
            set { 
                myBitSequence[index] = value;
                ParseBitSeq();  // should only call once.. overhead
            }
        }
        
        public void FlipBitMutate(float muRate)
        {
            for (int i = 0; i < myLength; i++)
            {
                double randValue = RandomGrabber.NextDouble();

                if (randValue <= muRate)
                {
                    myBitSequence[i] = RandomGrabber.Next(2);
                }
            }
            ParseBitSeq();
        }

        public void SwapBitsMutate(float muRate)
        {
            for (int i = 0; i < myLength; i++)
            {
                double randValue = RandomGrabber.NextDouble();

                if (randValue <= muRate/2.0)
                {
                    int index1 = RandomGrabber.Next(myLength);
                    int index2 = RandomGrabber.Next(myLength);

                    int temp = myBitSequence[index1];
                    myBitSequence[index1] = myBitSequence[index2];
                    myBitSequence[index2] = temp;
                }
            }
            ParseBitSeq();
        }

        public void Randomize()
        {
            for (int i = 0; i < myLength; i++)
                myBitSequence[i] = RandomGrabber.Next(2);

            ParseBitSeq();
        }

        public void Clear()
        {
            for (int i = 0; i < myLength; i++)
            {
                myBitSequence[i] = 0;
            }
            ParseBitSeq();
            myFitness = 0;
        }


        private void ParseBitSeq()
        {
            int ct = 0;
            for (int i = 0; i < numOfAlleles; i++)
            {
                for (int j = 0; j < alleleList[0].Length; j++)
                {
                    alleleList[i][j] = myBitSequence[ct];
                    ct++;
                }
            }
        
        }

        private void AssembleBitSeq()
        {
            int ct = 0;
            for (int i = 0; i < numOfAlleles; i++)
            {
                for (int j = 0; j < alleleList[0].Length; j++)
                {
                    myBitSequence[ct] = alleleList[i][j];
                    ct++;
                }
            }
       
        }

        public override string ToString()
        {
            string temp = "Chromosome ID: [" + myID + "] \r\n" ;
            
            temp += "        Genotype:    [ ";

            for (int i = 0; i < numOfAlleles; i++)
            {
                for (int j = 0; j < alleleList[0].Length; j++)
                {
                    temp += alleleList[i][j].ToString();
                }
                if (i != numOfAlleles - 1)
                    temp += " | ";
                else
                    temp += " ] \r\n";
            }

            temp += "        Phenotype: ";

            for (int i = 0; i < numOfAlleles; i++)
            {
                temp += " [ ";
                temp += alleleList[i].Value.ToString("N3");
                temp += " ] ";
            }



            temp += "\r\n        Fitness:                      " +  this.Fitness.ToString("N3");
            temp += "\r\n        Scaled Fitness:           " +  this.ScaledFitness.ToString("N3");
            temp += "\r\n        Accumulated Fitness:  " +  this.AccumulatedFitness.ToString("N3");

            return temp;
        }
    }


    public class MaxChromComparer : IComparer
    {
        public MaxChromComparer() {}

        public int Compare(object x, object y)
        {
            if (!(x is Genome) || !(y is Genome))
                throw new ArgumentException("Not of type Genome");

            if (((Genome)x).ScaledFitness < ((Genome)y).ScaledFitness)
                return 1;

            else if (((Genome)x).ScaledFitness == ((Genome)y).ScaledFitness)
                return 0;

            else
                return -1;
        }
    }


    public class MinChromComparer : IComparer
    {
        public MinChromComparer() { }

        public int Compare(object x, object y)
        {
            if (!(x is Genome) || !(y is Genome))
                throw new ArgumentException("Not of type Genome");

            if (((Genome)x).ScaledFitness > ((Genome)y).ScaledFitness)
                return 1;

            else if (((Genome)x).ScaledFitness == ((Genome)y).ScaledFitness)
                return 0;

            else
                return -1;
        }
    }

}
